package gov.nist.ssd.swqb.core.search;

import static gov.nist.ssd.swqb.config.DebugMode.dClassName;
import static gov.nist.ssd.swqb.config.DebugMode.dMethodName;
import static gov.nist.ssd.swqb.config.DebugMode.dRequestGoal;
import static gov.nist.ssd.swqb.config.DebugMode.dSparqlText;
import static gov.nist.ssd.swqb.config.DebugMode.debugModeIsOn;
import static gov.nist.ssd.swqb.config.DebugMode.printSparqlDebugInformations;
import gov.nist.ssd.swqb.core.exceptions.UnexpectedSearchResult;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.Syntax;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFNode;

abstract class AbstractSelectSearch extends AbstractSearch {

  protected AbstractSelectSearch(SearchText searchText, Model model,
      int requestsSize, VarName varName, RequestPattern searchPattern) {
    super(searchText, model, requestsSize, varName, searchPattern);
  }

  @Override
  public Iterator<RDFNode> iterator() {
    return new Iterator<RDFNode>() {
      
      private int offset = 0;
      private boolean done = false;
      private List<RDFNode> nodeCache = null;
      
      @Override
      public boolean hasNext() {
        // first call
      	if(nodeCache == null) {
          nodeCache = getMore();
        }
        
      	// other calls
      	if(nodeCache.size()==0) {
      		if(!done) {
      			nodeCache = getMore();
      		} else {
      			return false;
      		}
      	}
      	
        return nodeCache.size()!=0;
      }

      @Override
      public RDFNode next() {
        if(hasNext()) {
          RDFNode res = nodeCache.get(0);
          nodeCache.remove(0);
          return res;
        } else {
          throw new NoSuchElementException();
        }
      }

      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
      
      private List<RDFNode> getMore(int n) {
        
        List<RDFNode> result = new ArrayList<RDFNode>(getResSize());
        
        // prepare query
        String completeQueryString = getCompleteQueryString(n);
        
        if(debugModeIsOn()) {
          printSparqlDebugInformations(
              dClassName(getOuter().getClass().toString()+"$1"),
              dMethodName("getMore"),
              dRequestGoal("get mode search results"),
              dSparqlText(completeQueryString));
        }
        
        Query query = QueryFactory.create(completeQueryString, Syntax.syntaxARQ);
        QueryExecution qexec = QueryExecutionFactory.create(query, getModel());
        ResultSet resultSet = qexec.execSelect();
        // extract results
        while(resultSet.hasNext()) {
          RDFNode node = resultSet.next().get(getVarName().toString());
          
          if(node==null) {
            throw new UnexpectedSearchResult();
          }
          
          result.add(node);
        }
        
        // clean up
        qexec.close();

        // update offset
        offset += result.size();
        
        // done ?
        if(result.size() != n) {
        	done = true;
        }
        
        return result;
      }
      
      private List<RDFNode> getMore() {
        return getMore(getResSize());
      }
      
      private String getCompleteQueryString(int n) {
        String completeQueryString =
            getBaseQuery() + " LIMIT " + getResSize() + " OFFSET " + offset + "\n";

        return completeQueryString;
    }
    };
  }
  
}
